<template>
  <div class="main-container1" :style="'height:' + height">
    <div class="main-card">


      <div class="card-content">
        <div style="width:100%;">
          <el-drawer title="问答列表" :visible.sync="hisListDis" :show-close="false" size="20%" direction="ltr">
            <div class="history-list">
              <div v-for="(item, index) in historyList" :key="index" class="history-item"
                :class="{ active: currentSessionId === item.id }" @click="selectSession(item.id)">
                <div class="history-preview">{{ item.title }}</div>
              </div>
            </div>

          </el-drawer>

          <!-- 配置弹窗 -->
          <el-drawer :visible.sync="setDis" :show-close="false" size="20%" direction="rtl">
            <div style="padding:25px 20px;">
              <div>
                <span class="demonstration">知识库返回限制</span>
                <el-slider style="width:90%;margin:0 auto;" v-model="localLimit" :step="1" :min="1" :max="10"
                  :marks="marksLimit" show-stops>
                </el-slider>
              </div>
              <el-divider />
              <div>
                <span class="demonstration">特色人声朗读</span>
                <el-row style="padding:18px 40px;" class="auto-audio-role rensheng">
                  <el-button v-for="item in audioRole" :type="item.sex == 'F' ? 'danger' : 'primary'"
                    :plain="!item.checked" @click="audioRoleUp(item)"><img :src="item.url" />{{ item.label
                    }}</el-button>
                </el-row>


                <el-row style="padding:18px 40px;" class="auto-audio-role">
                  <span class="demonstration" style="color:#72767b;">是否默认开启</span>
                  <el-switch style="float:right;" v-model="isSetAudio">
                  </el-switch>
                </el-row>

              </div>

              <!-- <div>
            <div style="padding-bottom: 10px;"><span class="demonstration">切换源</span></div>
            <el-radio-group v-model="fetchKey">
              <el-radio-button label="local" key="local">本地部署</el-radio-button>
              <el-radio-button label="deepseek" key="deepseek">云端对话</el-radio-button>
            </el-radio-group>
          </div> -->
            </div>
          </el-drawer>

          <!-- 主界面 -->
          <div class="main-content">
            <!-- <div v-if="currentMessages.length < 2" style="position:absolute;top:35%;left:0;right:0;">
              <img v-if="isLogo" :src="logoUrl" alt=""
                style="width: 20%;margin:0 auto;display:block;width:150px;opacity: 0.15;">
              <div style="width:230px;margin:0 auto;color:#888;font-size: 15px;margin-top: 15px;text-align: center;">
                {{ subtitle }}</div>
            </div> -->
            <!-- 消息容器 -->
            <div class="message-container" ref="messageContainer">
              <div v-for="(message, index) in currentMessages" :key="index" class="message-item" :class="message.type"
                v-if="message.isShow && message.content.length">
                <div class="message-avatar" v-if="message.type === 'ai'"><img :src="audioRoleKey.url" alt="AI"></div>
                <!-- <div class="message-avatar pc" v-else><img :src="avatar" alt="User"></div> -->

                <div class="message-bubble">
                  <div v-if="isShowThink && message.type === 'ai'" @click="message.thinkDis = !message.thinkDis"
                    style="display: flex;margin-bottom: 10px;">
                    <div v-if="message.generating" class="loading-spinner">
                      <div class="spinner"></div>
                    </div>
                    <div style="line-height:30px;padding-left:15px;font-size: 15px;color: #727272;"> <span
                        v-if="message.generating">思考中... </span><span v-if="!message.generating">思考完成</span></div>
                  </div>

                  <div class="message-text">
                    <div v-if="message.type === 'ai' && message.thinkDis" class="think"
                      v-html="upContent(message.think)"></div>
                  </div>
                  <div class="message-text" v-if="message.type != 'human'" v-html="upContent(message.content)"
                    :style="{ fontSize: fontSize + 'px', lineHeight: fontLine }"></div>
                  <div class="message-text" v-else v-html="upContent(message.response_metadata.ques)"></div>

                  <!-- 音频按钮 -->
                  <el-button v-if="isRead && message.type === 'ai' && !message.generating && index > 1"
                    style="width:30px;margin:5px 0;" class="play-button"
                    :type="isAudioStart.id == index && isAudioStart.is ? 'danger' : 'success'"
                    :icon="isAudioStart.id == index && isAudioStart.is ? 'el-icon-video-pause' : 'el-icon-video-play'"
                    circle size="mini"
                    @click="isAudioStart.id == index && isAudioStart.is ? clickAudioBtn('audioStop',index) : clickAudioBtn('audioStart',index)" />

                  <!-- marked.parse(message.content) -->
                  <!-- <div v-if="message.role === 'system'" class="">
                    <el-divider></el-divider>
                    <div class="rainbow-text" style="padding-bottom:15px;"><span class="icon iconfont icon-dengpao1"
                        style="width:25px;color: #5151EE;"></span>&emsp;猜你想问</div>
                    <div class="p-div p-dian" style="color:#1890ff">出生证明怎么办理？</div>
                    <div class="p-div p-dian" style="color:#1890ff">社保怎么补办？</div>
                  </div> -->
                  <!-- <div v-if="message.role === 'assistant' || message.role === 'system'" class="message-time">{{ message.time
              }}</div> -->
                  <!-- <div v-else class="message-time" style="color:#fff;">{{ message.time }}</div> -->
                </div>

              </div>
            </div>


            <!-- 输入区域 -->
            <div class="input-div">
              <div v-if="hotQues.length" class="hotQuesDiv">
                <div v-for="(qq, i) in hotQues" @click="inputQues(qq)">
                  {{ qq.title }}
                  <span v-if="i < 2">🔥</span>
                </div>
              </div>
              <div class="input-bg" :style="'padding:' + inputPadd">

                <div v-if="inputStatus == 'yuyin'" style="position:flex;min-height: 46px;" class="pt5 pb5">
                  <!-- 语音识别显示文字 -->
                  <div v-if="inputMessage" style="padding-bottom:10px;color:#606266;">{{ inputMessage }}
                  </div>
                  <!-- 打字语音切换按钮 -->
                  <button @click="upInputStatus('dazi')" class="" style="padding-right: 5px;"><span
                      class="iconBtn icon iconfont icon-icon-dazi"
                      style="font-size: 26px;line-height: 36px;" /></button>

                  <!-- 本地的语音按钮,点击开始结束 -->
                  <div v-if="isSpeechType == 'local'">

                    <el-button v-if="!btnStartDis" @click="record" :disabled="btnStartDis" type="primary" size="mini"
                      round plain style="position:absolute;left:calc(50% - 76px);width:150px;bottom:2px;">点击
                      说话</el-button>
                    <el-button v-if="!btnStopDis" @click="stop" :disabled="btnStopDis" type="danger" size="mini" round
                      plain style="position:absolute;left:calc(50% - 76px);width:150px;bottom:2px;">点击 停止</el-button>
                  </div>
                  <!-- 腾讯的语音按钮,长摁识别,松开结束 -->
                  <TencentSpeech v-else @tencent-speech-resultText="tencentSpeechResultText"
                    @tencent-speech-stop="upInputStatus('dazi')"
                    btnStyle="background:rgb(235, 135, 162);height:35px;width:240px;"
                    style="position:absolute;left:calc(50% - 110px);bottom:6px;" color="rgba(235, 135, 162,0.6)" />
                </div>

                <div v-else style="display:flex;min-height: 46px;" class="pt5 pb5">
                  <button v-if="isSpeech" @click="upInputStatus('yuyin')" class="" style="padding-right: 5px;"><span
                      class="iconBtn icon iconfont icon-yuyin1" style="font-size: 30px;line-height: 36px;" /></button>
                  <el-input class="inputDivNone" size="medium" :disabled="!isEnd" type="text"
                    :autosize="{ minRows: 1, maxRows: 5 }" :clearable="true"
                    :placeholder="isEnd ? '输入你的问题↩︎点击回车提交' : ''" v-model="inputMessage" resize="none"
                    @keydown.enter.native.prevent="submitQues">
                  </el-input>
                  <el-button class="btnNone" v-if="!isEnd" size="medium" @click="cancelRequest()"><span
                      class="iconBtn icon iconfont icon-16gf-stopCircle" style="font-size: 29px;" /></el-button>
                  <el-button class="btnNone" v-if="isEnd" size="medium" @click="submitQues()"><span
                      class="iconBtn icon iconfont icon-fasonganniu"
                      :style="{ 'font-size': '29px', 'color': themeColor }" /></el-button>
                </div>

              </div>

              <div style="padding:10px 5px 0;" v-if="isNewConvo || isMenu || isConfig">
                <el-button v-if="isNewConvo"
                  style="padding:2px 10px;border:2px solid #9D9D9D;background:none;border-radius: 20px;float:left;"
                  class="" size="medium" @click="createNewSession">
                  <span class="iconBtn icon iconfont icon-a-lujing53834"
                    :style="'font-size: 18px;color:' + themeColor + ';float:left;width:auto;height:auto;'"></span>
                  <div style="line-height: 20px;font-size: 13px;padding-left:25px;color:#999;">新对话</div>
                </el-button>


                <div style="float:right;">
                  <el-button v-if="isMenu" class=""
                    style="border:0;padding:0;background:none;float:left;margin-top: 2px;" @click="hisListDis = true">
                    <span class="iconBtn icon iconfont icon-caidan1" style="font-size: 24px;" />
                  </el-button>

                  <el-button v-if="isConfig" style="border:0;padding:0;background:none;" size="medium"
                    @click="setDis = true"><span class="iconBtn icon iconfont icon-shezhi"
                      style="font-size: 29px;" /></el-button>
                </div>
              </div>

            </div>


          </div>
          <div class="stAudio">
            <audio id="myAudio" :controls="true" />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>



<script>


import { getData } from "@/api/localserver/data";
// import { getIpConfig } from "@/api/wenda/ipConfig";
// import { addConfig } from "@/api/wenda/jsonConfig";
// import { getConfig } from "@/api/wenda/homeConfig";
// import { listDeepseek, getDeepseek, addDeepseek, deleteDeepseek } from "@/api/wenda/deepseek";

import Recorder from 'recorder-core';
import '@/assets/js/wav';
import '@/assets/js/pcm';

// import tospeech from '@/api/wenda/tospeech.js'; // 引入 tospeech.js
import tospeech from '@/api/tencent/tts.js'; // 引入 tospeech.js
import markdown from '@/utils/wenda/markdown.js'; // 引入定义样式后的markdown
import wss from "@/api/wenda/wss.js"; // 引入初始化方法
import langC from '@/api/wenda/langChain.js';

import aiNv from '@/assets/images/ai-role/nv.jpg';
import aiNan from '@/assets/images/ai-role/nan.jpg';
import { mapGetters } from "vuex";

import TencentSpeech from '@/components/Tencent/TencentSpeech.vue'


export default {
  computed: {
    ...mapGetters(["token", "user"]),
  },
  components: { TencentSpeech },

  props: {
    /**
     * API接口地址
     */
    api: {
      type: String,
      default: ''
    },
    assisId: {
      type: String,
      default: ''
    },
    quesCarry: {
      type: Object,
      default: () => ({ format: '', first: '', always: '' })
    },
    hotQues: {
      type: Array,
      default: () => []
    },
    /**
     * 组件高度
     */
    height: {
      type: String,
      default: '100vh'
    },
    /**
     * 主题颜色
     */
    themeColor: {
      type: String,
      default: '#0395F8'
    },
    /**
     * 是否显示思考过程
     */
    isShowThink: {
      type: Boolean,
      default: false
    },
    /**
     * 是否显示新建对话按钮
     */
    isNewConvo: {
      type: Boolean,
      default: false
    },
    /**
     * 是否显示配置按钮
     */
    isConfig: {
      type: Boolean,
      default: false
    },
    /**
     * 是否显示菜单按钮
     */
    isMenu: {
      type: Boolean,
      default: false
    },
    /**
     * 是否启用语音输入功能
     */
    isSpeech: {
      type: Boolean,
      default: false
    },
    isSpeechType: {
      type: String,
      default: 'local'
    },
    /**
     * 是否启用语音朗读功能
     */
    isRead: {
      type: Boolean,
      default: false
    },
    /**
     * 语音朗读的默认文本
     */
    readText: {
      type: String,
      default: '您好，AI助手为您服务，请输入您的问题，我帮您快速解答。'
    },
    /**
     * 是否显示Logo
     */
    isLogo: {
      type: Boolean,
      default: false
    },
    /**
     * Logo图片URL
     */
    logoUrl: {
      type: String,
      default: function () {
        return require('@/assets/images/mr/logo.png')
      }
    },
    /**
     * 副标题文本
     */
    subtitle: {
      type: String,
      default: '我可以帮你搜索、答疑，请把你的任务交给我吧~'
    },
    /**
     * 输入框内边距
     */
    inputPadd: {
      type: String,
      default: '20px'
    },

  },
  data() {
    return {
      // texttospeech  
      audioRole: [
        { "voice": 1088, checked: false, label: "女声01", "prompt": "开心", sex: "F", url: aiNv, "rm": "正常" },
        { "voice": 102, checked: false, label: "女声02", "prompt": "开心", sex: "F", url: aiNv, "rm": "正常，太绵" },
        { "voice": 1422, checked: false, label: "女声03", "prompt": "开心", sex: "F", url: aiNv, "rm": "机器味" },
        { "voice": 7000, checked: false, label: "男声", "prompt": "开心", sex: "M", url: aiNan, "rm": "正常，呆" },
      ],
      audioRoleKey: {},

      // 音频文件数组
      audioSrc: [],
      toSpeechtest: /[？。！（]/,
      toSpeechKey: 0,
      // 播放下标
      audioAutoKey: 0,
      // 是否默认开启音频
      isSetAudio: true,
      // 是否有正在播放的音频
      isAutoAudio: false,
      // 本次播放的音频
      audioOdj: null,
      isUserEnd: false,
      isAudioStart: { id: 0, is: false },

      // 语音
      inputStatus: "dazi",
      Uri: "/wssapi/",
      // Uri:"ws://192.168.97.10:10096/",
      btnStartDis: true,
      btnStopDis: true,
      wsconnecter: null,
      isRec: false,
      use_itn: true, // 逆文本标准化(ITN) true false
      asr_mode: "2pass",  // 选择asr模型模式  2pass  online  offline
      isfilemode: false, // 是否为文件模型
      sampleBuf: new Int16Array(),
      file: "", // 文件需要上传解析
      chunk_size: new Array(8, 16, 8),
      rec_text: "",
      offline_text: "",


      // 问答配置
      fetchKey: "local",
      fetchConfig: {
        deepseek: {
          url: "/deep/chat/completions",
          // url:"/deep/chat/completions",
          model: "deepseek-reasoner",
          apikey: 'Bearer sk-df0831cae1e446e3a1b20e32d6e597f9'
        },
        local: {
          // url:"http://macmini:11434/v1/chat/completions",
          // url:"/mac/v1/chat/completions",
          // model:"deepseek-r1:14b",
          // apikey:'ollama'
          url: "https://api.siliconflow.cn/v1/chat/completions",
          model: "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B",
          apikey: 'Bearer sk-taalmohasunalwcsqnjgpnsfdxqeuofkhnbimbmzlcptdgld'
        }
      },
      marksLimit: {
        1: '1',
        2: '2',
        3: '3',
        4: '4',
        5: '5',
        6: '6',
        7: '7',
        8: '8',
        9: '9',
        10: '10',
      },
      localLimit: 3,

      sidebarCollapsed: true, // 左侧列表伸缩-true收缩
      hisListDis: false,  // 左侧列表显示状态-false隐藏
      setDis: false,
      inputMessage: '', // 输入框内容
      historyList: [{
        id: 1,
        title: '新问题',
        preview: '...',
        messages: []
      }],
      currentMessages: [],
      currentSessionId: 1,
      // 打字效果
      isGenerating: false, // 打字状态
      displayContent: "",
      typingSpeed: 30, // 打字速度（毫秒/字）
      maxBubbleWidth: '70%',

      minBubbleHeight: '60px',
      // 流式消息
      isEnd: true,
      isUserEnd: false, // 用户终止的标志
      decoder: null,
      controller: null, // 存储 AbortController 实例
      isThink: false,
      isContent: false,
      thread: null,

      productInfo: null,

      // 文字展示样式
      fontSize: 16,
      fontLine: 1.5,

      audioContext: null,
    }

  },
  computed: {
    // ...mapGetters([
    //   'avatar',
    // ]),
    sidebarWidth() {
      return this.sidebarCollapsed ? '110px' : '15%'
    },
    currentMessages() {
      const session = this.historyList.find(
        item => item.id === this.currentSessionId
      )
      return session ? session.messages : []
    }
  },
  created() {
  },
  // 组件销毁时清理资源
  beforeDestroy() {
    if (this.audioContext) {
      this.audioContext.close();
    }
    this.currentSource = null;
  },
  mounted() {
    this.fontSize = localStorage.getItem('fontSize') ? parseInt(localStorage.getItem('fontSize')) : 16
    this.fontLine = localStorage.getItem('fontLine') ? parseFloat(localStorage.getItem('fontLine')).toFixed(1) : 1.5
    this.init()
    console.log('mounted', this.quesCarry)

  },
  watch: {
    isSetAudio(newVal, oldVal) {
      localStorage.setItem('wdSet-isSetAudio', newVal)

    },
    localLimit(newVal, oldVal) {
      localStorage.setItem('wdSet-localLimit', newVal)

    },
    audioRoleKey(newVal, oldVal) {
      localStorage.setItem('wdSet-audioRoleKey', JSON.stringify(newVal))
    },
    isConfig(newVal, oldVal) {
      console.log('isConfig changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    isMenu(newVal, oldVal) {
      console.log('isMenu changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    isNewConvo(newVal, oldVal) {
      console.log('isNewConvo changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    isLogo(newVal, oldVal) {
      console.log('isLogo changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    logoUrl(newVal, oldVal) {
      console.log('logoUrl changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    subtitle(newVal, oldVal) {
      console.log('subtitle changed:', newVal);
      // 可以在这里添加处理逻辑
    },
    currentMessages: {
      handler() {
        this.scrollToBottom();
      },
      deep: true
    }
  },
  methods: {
    clickAudioBtn(func,index){
      if(func == 'audioStop') this.isAudioStart.is = false
      else this.isAudioStart.is = true
      // this.isAudioStart.is = true
      this[func](index)
    },
    tencentSpeechResultText(text) {
      console.log('tencentSpeechResultText', text)
      this.inputMessage = text
    },
    inputQues(qq) {
      this.inputMessage = qq.ques
      if (qq.clickCount) {
        qq.clickCount = parseInt(qq.clickCount) + 1
      }
      this.$emit('hotQues-click', qq)
    },
    // 进入页面创建线程后，提交初始数据
    async AppQues() {
      this.inputMessage = ''
      let quesStr = ''
      // 开始处理提问的内容
      if (this.quesCarry.format != '') {
        //  quesCarry.format  '用户提问：{quesStr}. 首次携带：{firstData}. 始终携带：{alwaysData}. 实际问答中根据用户的意向调整推荐产品'
        quesStr = this.quesCarry.format.replace('{quesStr}', this.inputMessage).replace('{firstData}', this.quesCarry.first).replace('{alwaysData}', this.quesCarry.always)
      }

      // 增加字段？直接判断之前有没有用户问答信息？
      // { type: "ai", name: "", content: " ", generating: true }
      let msgArr = [{ type: "human", name: "user", content: quesStr, response_metadata: { ques: this.inputMessage }, assistant_id: this.agent.assistant_id, isShow: false, isSub: true },]
      this.currentMessages.push(...msgArr)

      console.log(this.currentMessages)


      this.thread = await langC.threadCreate({
        "thread_id": "",
        "metadata": { graph_id: "agent", ques: msgArr[0].response_metadata.content, openId: this.CustomerInfo.openId, deptId: this.CustomerInfo.deptId, type: "user", key: this.selVal.value },
        "if_exists": "raise",
      })


      // 提交问题后，带入线程,无线程时,重新生成
      // if (!this.thread) {
      //   this.thread = await langC.threadCreate({
      //     "thread_id": "",
      //     "metadata": { graph_id: "agent", ques: msgArr[0].content, openId: this.CustomerInfo.openId, deptId: this.CustomerInfo.deptId, type: "user", key: this.selVal.value },
      //     "if_exists": "raise",
      //   })
      // }
      // 语音预回答
      // this.fetchToSpeech({ text: "", to_i: -1, msg_i: 0, voice: this.audioRoleKey.voice, prompt: this.audioRoleKey.prompt, msg_content: "好的，我来为您解答疑问。", all: true, toSpeechtest: 0, form: "getLocalData", })
      let localData = null
      // if(this.isLocalData){
      //   localData = await this.getLocalData(msgArr[0]); // 等待 Promise 完成
      //   if(!localData){
      //     this.$modal.msgWarning("本地数据获取失败");
      //   }
      // }
      // this.chatUserCLickStream( localData)
    },
    init() {
      localStorage.getItem('CustomerInfo') && (this.CustomerInfo = JSON.parse(localStorage.getItem('CustomerInfo')));
      console.log("CustomerInfo", this.CustomerInfo)
      this.initAudioContext()
      // 定义一个utf-8格式的文本解码器
      this.decoder = new TextDecoder('utf-8')
      // 获取本地存储的值
      localStorage.getItem('wdSet-localLimit') !== null && (this.localLimit = +localStorage.getItem('wdSet-localLimit'))
      localStorage.getItem('wdSet-isSetAudio') !== null && (this.isSetAudio = Boolean(localStorage.getItem('wdSet-isSetAudio')))
      // localStorage.getItem('wdSet-isLocalData') !== null && (this.isLocalData = Boolean(localStorage.getItem('wdSet-isLocalData')))



      // 从 localStorage 获取音频角色配置
      const rolekey = localStorage.getItem('wdSet-audioRoleKey')
      console.log(rolekey)
      const audioRoleData = rolekey ? JSON.parse(rolekey) : this.audioRole[0]
      this.audioRoleUp(audioRoleData)


      // let id = this.$route.query.id
      // if (id) {
      if (this.assisId) {
        // getConfig(id).then(res => {
        //   this.homeConfig = res.data
        // this.selVal = { value: res.data.key, label: res.data.title, msg: res.data.cueWord }
        this.selVal = { value: "meirong", label: "", msg: "您好，我是您的美容顾问，任何问题都可以向我咨询。" }
        this.homeConfig = { assisId: this.assisId }
        this.initLang()
        // })
      }
      // getIpConfig().then(res => {
      //   this.openId = res.data.uuid
      //   console.log("生成唯一uuid", this.openId)
      //   if (!this.openId) {
      //     this.$modal.msgWarning("唯一ID未生成")
      //   }
      // })

    },
    async initLang() {
      // this.fetchToSpeech({ text: "", to_i: -1, msg_i: 0, voice: this.audioRoleKey.voice, prompt: this.audioRoleKey.prompt, msg_content: this.selVal.msg, all: true, toSpeechtest: this.toSpeechtest, form: "audioStart", })


      console.log("initLang", window.location)
      this.currentMessages = [{
        content: `<img src="${require('@/assets/images/mr/00.jpg')}" style="max-width: 100%; height: auto;"/>\n
30-35 岁年龄段人群面临的皮肤老化皱纹增多问题，主要源于以下原因：\n
1. 皮肤真皮层内胶原蛋白含量开始加速流失，年均流失率约 1%-2%，导致皮肤支撑力下降，出现细纹（如眼周纹、抬头纹）、干纹，部分人群出现明显法令纹初显；
2. 随着年龄增长，皮肤新陈代谢速率减缓，角质层更新周期延长，肌肤锁水能力减弱，干燥问题加剧，进一步诱发皱纹加深；
3. 受紫外线照射、熬夜、压力大、饮食不规律等外界因素影响，皮肤自由基增多，加速胶原蛋白降解，加重皮肤老化痕迹，表现为皮肤弹性降低、皱纹纹路变清晰。\n
## 一、其他用户的真实案例
<img src="${require('@/assets/images/mr/0/1 (2).jpg')}" style="max-width: 100%; height: auto;"/>\n
          `, type: 'ai', name: 'system', isShow: true, isSub: false
      }]

      // this.assistants = new AssistantsClient(config);
      // this.threads = new ThreadsClient(config);
      // this.runs = new RunsClient(config);
      // this.crons = new CronsClient(config);
      // this.store = new StoreClient(config);
      try {
        const assistants = await langC.agentList({ graphId: "retrieval_graph", limit: 100 }) // indexer
        console.log("assistants:", assistants)
        if (!assistants) {
          //   this.agent = await langC.agentCreate({ graphId: "agent" })
          this.$modal.msgError("AI助手链接出错，请检查网络或服务")
          // return false
        } else {
          if (this.homeConfig.assisId) {

            for (let i = 0; i < assistants.length; i++) {
              const item = assistants[i];
              if (item.assistant_id == this.homeConfig.assisId) {
                this.agent = item;
                break; // 找到匹配项后立即退出循环
              }
            }
          } else {
            this.agent = assistants[0] // 找不到对应助手，随便赋值一个
          }
          // 有助手信息的情况下，才执行历史信息
          this.historyList = await langC.threadList({ status: "idle", offset: 0, limit: 1, metadata: { openId: this.openId, type: "user", key: this.selVal.value, assistant_id: this.agent.assistant_id } })
        }
        console.log("agent:", this.agent)
        this.AppQues()
        // this.historyList = []
      } catch (error) {
        console.error("agent初始化错误: ", error);
      }
      // 初始化 WebSocket 连接
      this.wsconnecter = wss.initWebSocket({ file: this.file, stateHandle: this.getConnState, msgHandle: this.getJsonMessage, use_itn: this.use_itn, asr_mode: this.asr_mode, isfilemode: this.isfilemode, hotwords: this.hotwords });

      // 最后初始 开屏交互后播放语音
      this.fetchToSpeech({ text: "", to_i: -1, msg_i: 0, voice: this.audioRoleKey.voice, prompt: this.audioRoleKey.prompt, msg_content: this.selVal.msg, all: true, toSpeechtest: this.toSpeechtest, form: "audioStart", })

    },
    // 初始化Web Audio上下文（只执行一次）
    initAudioContext() {
      if (!this.audioContext) {
        // 兼容浏览器前缀（iOS Safari需要webkit前缀）
        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
      }
    },
    // 执行文字转语音的方法
    async fetchToSpeech(data) {
      if (!this.isRead) {
        return
      }
      if (!this.isAudioStart.is) {
        console.log("fetchToSpeech -- 当前没有播放音频状态，停止调用")
      }
      // data.text = data.text.replace('并且以中文回复，回复内容中不包含附件参考信息', '');
      let res = await tospeech.sendPostRequestApi(data);
      // console.log("res:", res)
      if (res.audioInit) {
        this.audioStop(res.msg_i)
        this.audioInit(res.msg_i)
      }
      this.audioSrc[res.to_i] = res.audioSrc;
      // 检查是否需要启动音频播放，且确保只调用一次播放音频
      if (res.audioStart && !this.audioAutoKey && !this.isAutoAudio) {
        // 如果 res.audioStart 为 true，并且当前没有自动播放音频的状态，则执行以下操作
        // 更新 isAudioStart 状态，标记当前消息的音频播放状态
        this.isAudioStart = res.isAudioStart;
        // 将返回的音频源数据存储到 audioSrc 数组中，索引为 res.to_i
        // console.log("朗读前打印 sendPostRequest001 ：", this.audioSrc);
        // 调用 audioStart 方法，启动指定消息索引的音频播放
        this.audioStart(res.msg_i);
      }
      if (this.currentMessages[res.msg_i] && res.all) {
        this.toSpeechKey++
        // console.log("重新调用：",'',this.toSpeechKey,msg_i,true)
        res.audioInit = false
        res.audioSrc = {}
        res.audioStart = false
        res.to_i++
        // console.log("重新调用", res)
        this.fetchToSpeech(res)
      }
      console.log(this.audioSrc)
    },
    // 播放音频
    async audioStart(msg_i) { // 自动播放音频

      console.log("audioStart调用--问答下标：", msg_i, "--当前数据：", this.isAudioStart, "--语音数据：", this.audioSrc, "--播放下标：", this.audioAutoKey)
      if (!this.isAudioStart.is) {
        console.log("fetchToSpeech -- 当前没有播放音频状态，停止调用")
        return
      }
      // console.log("audioStart --问答下标：",msg_i,"当前数据：",this.isAudioStart,"--语音数据：",this.audioSrc,"--播放下标：",this.audioAutoKey)
      if (msg_i != this.isAudioStart.id && this.isEnd) { // 数据不匹配，没有正在回答的问题
        // console.log("audioStart,重新加载语音数据")
        // this.sendPostRequest('',-1,msg_i,true,"audioStart")
        this.fetchToSpeech({
          text: "",
          to_i: -1,
          msg_i: msg_i,
          voice: this.audioRoleKey.voice,
          prompt: this.audioRoleKey.prompt,
          msg_content: this.currentMessages[msg_i].content,
          all: true,
          toSpeechtest: this.toSpeechtest,
          form: "audioStart",
        })
        return false
      }
      // 自动播放音频
      if (!this.audioSrc[this.audioAutoKey] || !this.audioSrc[this.audioAutoKey].src) { // 当前播放音频为空，重新播放，直至音频存在
        // console.log("audioStart==this.audioSrc[this.audioAutoKey]==",this.audioSrc[this.audioAutoKey])
        setTimeout(() => {
          this.audioStart(msg_i)
        }, 1000);
        return false
      }


      try {
        // 关键：激活音频上下文（iOS必须用户交互触发，这里在audioStart中执行，确保是点击触发）
        if (this.audioContext.state !== "running") {
          await this.audioContext.resume();
        }
        console.log(this.audioContext)

        // 加载当前音频为Web Audio缓冲区
        const buffer = await this.loadAudioToBuffer(this.audioSrc[this.audioAutoKey].src);
        // 播放缓冲区
        this.playAudioBuffer(buffer, msg_i);
      } catch (error) {
        console.error("播放失败：问答下标：", msg_i, "--当前播放KEY：", this.audioAutoKey, error);
        this.isAudioStart = { id: msg_i, is: false };
        // 原有重试逻辑
        setTimeout(() => {
          this.audioStart(msg_i);
        }, 2000);
      }



      // this.audioOdj = new Audio(this.audioSrc[this.audioAutoKey].src);
      // this.audioOdj.addEventListener('ended', () => {
      //   setTimeout(() => {
      //     this.audioAppend(msg_i)
      //   }, 500);
      // });
      // this.audioOdj.controls = true
      // this.audioOdj.id = "myAudio";
      // this.audioOdj.className = "stAudio";
      // document.getElementById('myAudio').replaceWith(this.audioOdj)

      // this.audioOdj.play().then(() => {
      //   this.isAutoAudio = true
      //   // console.log("play",botMessage)
      //   this.isAudioStart = { id: msg_i, is: true }

      //   console.warn("语音开始播放 ******* 当前KEY", this.audioAutoKey, "总长：", this.audioSrc.length, this.audioSrc[this.audioAutoKey].text)
      // }).catch(error => {
      //   console.error("播放失败：问答下标：", msg_i, "--当前播放KEY：", this.audioAutoKey, error);
      //   console.log("语音数据：", JSON.stringify(this.audioSrc[this.audioAutoKey]));
      //   this.isAudioStart = { id: msg_i, is: false }
      //   setTimeout(() => {
      //     this.audioStart(msg_i)
      //   }, 2000);
      // });
    },
    // 将音频URL加载为Web Audio缓冲区
    async loadAudioToBuffer(url) {
      if (!this.isAudioStart.is) {
        console.log("fetchToSpeech -- 当前没有播放音频状态，停止调用")
        return
      }
      try {
        const response = await fetch(url);
        const arrayBuffer = await response.arrayBuffer();
        // 解码为可播放的音频缓冲区
        return await this.audioContext.decodeAudioData(arrayBuffer);
      } catch (error) {
        console.error("音频解码失败：", error);
        throw error; // 抛出错误让上层处理
      }
    },

    // 播放音频缓冲区（核心播放逻辑）
    playAudioBuffer(buffer, msg_i) {
      if (!this.isAudioStart.is) {
        console.log("fetchToSpeech -- 当前没有播放音频状态，停止调用")
        return
      }
      console.log("playAudioBuffer:", buffer, msg_i);
      // 停止当前可能存在的播放（避免重叠）
      if (this.currentSource) {
        this.currentSource.stop();
      }

      // 创建音频源节点（Web Audio播放核心）
      const source = this.audioContext.createBufferSource();
      source.buffer = buffer; // 绑定缓冲区
      source.connect(this.audioContext.destination); // 连接到扬声器

      // 记录当前播放节点，用于停止操作
      this.currentSource = source;

      // 播放状态更新（与原有逻辑对齐）
      this.isAutoAudio = true;
      this.isAudioStart = { id: msg_i, is: true };
      console.warn("语音开始播放 ******* 当前KEY", this.audioAutoKey, "总长：", this.audioSrc.length, this.audioSrc[this.audioAutoKey].text);

      // 监听播放结束，触发下一段（苹果设备不会拦截，因为上下文已激活）
      source.onended = () => {
        // 释放当前源节点引用
        this.currentSource = null;
        setTimeout(() => {
          this.audioAppend(msg_i); // 调用原有追加逻辑
        }, 500);
      };

      // 开始播放
      source.start(0);
    },
    audioAppend(msg_i) { // 播放音频追加调用
      console.log("audioAppend调用--问答下标：", msg_i, "--当前数据：", this.isAudioStart, "--语音数据：", this.audioSrc, "--播放下标：", this.audioAutoKey)
      if (!msg_i) {
        console.warn("播放完成")
        this.isAudioStart = { id: 0, is: false }
        this.audioInit(msg_i)
        return false
      }

      if (this.audioSrc[this.audioAutoKey] && this.audioSrc[this.audioAutoKey].src) { // 确认信息后删除
        URL.revokeObjectURL(this.audioSrc[this.audioAutoKey].src); // 释放已经播放完成的音频
      }
      let msgArr = this.currentMessages[msg_i].content.split(this.toSpeechtest);
      // console.log("audioAppend-追加播放时对比长度：msg_i：" + msg_i
      // + "，audioAutoKey：" + this.audioAutoKey
      // + "，audioSrc.length：" + this.audioSrc.length
      // + "，msgArr.length：" + msgArr.length)
      // msgArr
      // 当前播放下标超过实际文字截取后的长度，才会停止
      if (this.isEnd) {
        // 比对数据长度
        if (this.audioAutoKey >= this.audioSrc.length - 1 && this.audioSrc.length >= msgArr.length - 1) {
          // console.warn("audioAppend-setTimeout",msgArr,msgArr.length)
          console.warn("播放完成")
          this.isAudioStart = { id: msg_i, is: false }
          // console.log("&&&&&&&audioAppend修改isAudioStart",this.isAudioStart)
          this.audioInit(msg_i)
          return false
        }
      }
      setTimeout(() => {
        this.audioAutoKey++ //播放音频
        this.audioStart(msg_i)
      }, 500);
    },
    // 停止播放（适配Web Audio）
    audioStop(msg_i) {
      this.audioSrc = []
      this.audioAutoKey = 0
      this.isAudioStart = { id: msg_i, is: false }
      if (this.audioOdj) {
        this.audioOdj.pause();
        this.audioOdj = null;
      }
      this.audioInit(msg_i)
      // console.log("&&&&&&&audioStop修改isAudioStart", this.isAudioStart)
      // 停止当前Web Audio源节点
      if (this.currentSource) {
        this.currentSource.stop();
        this.currentSource = null;
      }
      // // 清空Audio元素（如果需要）
      // const audioEl = document.getElementById('myAudio');
      // if (audioEl) {
      //   audioEl.pause();
      // }
      // if (this.audioOdj) {
      //   this.audioOdj.pause();
      //   this.audioOdj = null;
      // }
    },
    audioInit(msg_i) {
      console.log("audioInit", msg_i)
      // 停止当前播放
      if (this.currentSource) {
        this.currentSource.stop();
        this.currentSource = null;
      }
      // 原有状态重置
      this.audioOdj = null
      this.toSpeechKey = 0
      this.audioAutoKey = 0
      this.isAutoAudio = false
      this.audioSrc = [] // blob数据已经逐个释放
      this.isAudioStart = { id: 0, is: false }
    },
    audioRoleUp(data) {
      this.audioRoleKey = data
      for (let i = 0; i < this.audioRole.length; i++) {
        const voice = this.audioRole[i].voice;
        if (voice == this.audioRoleKey.voice) {
          console.log(voice)
          this.audioRole[i].checked = true
        } else {
          this.audioRole[i].checked = false
        }
      }
    },
    // 录音开始
    upInputStatus(st) {
      if (this.isRec) {
        this.$modal.msgWarning("录音中，终止后使用语音功能");
        return false
      }
      if (!this.isEnd) {
        this.$modal.msgWarning("回答中，终止后可使用语音功能");
        return false
      }
      this.inputStatus = st
      if (st == 'yuyin') {
        // 使用本地的转语音时,执行
        if (this.isSpeechType == 'local') {
          this.start()
        }
      }
    },
    async record() { // (2)(点击录音/获取权限)点击按钮，开始调起录音
      const recorder = new Recorder({
        type: "pcm",
        bitRate: 16,
        sampleRate: 16000,
        numChannels: 1,  // 

        audioTrackSet: { // 新增音频轨道配置
          echoCancellation: true,    // 启用回声消除
          noiseSuppression: true,    // 启用噪声抑制
          autoGainControl: false,      // 禁用自动增益控制
          channelCount: 1 // 单声道
        },

        // echoCancellation: true,    // 启用回声消除
        // noiseSuppression: true,    // 启用环境噪声抑制
        // autoGainControl: true,     // 启用自动增益控制
        onProcess: this.recProcess,
      });
      recorder.open(() => {
        this.isRec = true
        this.btnStartDis = true;
        this.btnStopDis = false;
        console.log("record()(2)(点击录音/获取权限)开始录音")
        recorder.start();

      }, (error) => { // 新增错误回调参数
        // this.$modal.msgWarning(error);
        this.inputStatus = "dazi"
        this.$modal.msgError(error);
      });
      this.rec = recorder;

    },
    start() { // (1)(链接)识别启动、停止、清空操作
      this.clear();
      // 启动连接
      if (this.wsconnecter && typeof this.wsconnecter.wsStart === 'function') {
        try {
          var ret = this.wsconnecter.wsStart(this.Uri);
          // 1 is ok, 0 is error
          if (ret == 1) {
            console.log("进入start()(1)(链接)正常开始")
            // this.$modal.msgWarning("正在连接asr服务器，请稍后...")
            // this.inputStatus="dazi"
            this.btnStartDis = false;
            this.btnStopDis = true;
            console.log(401, this.btnStopDis)

            return 1;
          } else {
            console.log("进入start()出错，无法开始，连接失败")
            this.$modal.msgError("无法连接asr服务器，请重试")
            this.inputStatus = "dazi"
            this.btnStartDis = true;
            this.btnStopDis = true;
            console.log(411, this.btnStopDis)
            return 0;
          }
        } catch (error) {
          this.inputStatus = "dazi"
          this.btnStartDis = true;
          this.btnStopDis = true;
          console.log(418, this.btnStopDis)
          this.$modal.msgError(error)
          console.error('wsStart 调用失败: WebSocket 连接启动失败，请重试:', error);
        }
      } else {
        this.inputStatus = "dazi"
        this.btnStartDis = true;
        this.btnStopDis = true;
        console.log(428, this.btnStopDis)
        this.$modal.msgError("未正确初始化")
        console.error('wsconnecter 未正确初始化或 wsStart 方法不存在:WebSocket 连接器未正确初始化，请重试');
      }
    },
    stop() { // 停止连接
      if (this.wsconnecter && typeof this.wsconnecter.wsStop === 'function') {

        try {
          // 控件状态更新

          var request = {
            "chunk_size": this.chunk_size,
            "wav_name": "h5",
            "is_speaking": false,
            "chunk_interval": 10,
            "mode": this.asr_mode,
          };
          this.isAudioStart.id = 0
          this.isAudioStart.is = false
          console.log(request);
          if (this.sampleBuf.length > 0) {
            this.wsconnecter.wsSend(this.sampleBuf);
            console.log("sampleBuf.length" + this.sampleBuf.length);
            this.sampleBuf = new Int16Array();
          }
          this.wsconnecter.wsSend(JSON.stringify(request));

          console.log("发送完数据,请等候,正在识别...");
          this.isRec = false;


          if (!this.isfilemode) {
            this.inputStatus = 'dazi' // 停止录音，切回打字状态
            this.btnStopDis = true;
            console.log(442, this.btnStopDis)
            this.btnStartDis = true;
            // 等待 3s 后断开 WebSocket
            setTimeout(() => {
              console.log("call stop ws!");
              this.wsconnecter.wsStop();
              console.log("延时三秒断开，请点击连接");
            }, 3000);

            // 停止录音
            if (this.rec && typeof this.rec.stop === 'function') {
              this.rec.stop((blob, duration) => {
                console.log("停止录音,打印录音文件", blob);
                var audioBlob = Recorder.pcm2wav({
                  sampleRate: 16000,
                  bitRate: 16,
                  blob: blob
                }, (theblob, duration) => {
                  console.log(theblob);
                  let src = (window.URL || webkitURL).createObjectURL(theblob);
                  console.log(src)
                }, (msg) => {
                  console.log(msg);
                });
              }, (errMsg) => {
                console.log("errMsg: " + errMsg);
              });
            }
          }
        } catch (error) {
          console.error('WebSocket 连接停止失败，请重试  wsStop 调用失败:', error);
        }
      } else {
        console.error('wsconnecter 未正确初始化或 wsStop 方法不存在');
      }
    },
    getConnState(connState) { // 连接状态响应
      console.log("connState", connState);
      if (connState === 0) { // on open
        console.log('getConnState  连接成功!请点击开始');
        this.inputStatus = "yuyin"
        this.btnStartDis = false;
        this.btnStopDis = true;
      } else if (connState === 1) {
      } else if (connState === 2) {
        this.stop();
        console.log("连接地址" + this.Uri + "失败,请检查asr地址和端口。或试试界面上手动授权，再连接。");
        this.btnStartDis = true;
        this.btnStopDis = true;
        // console.log(499,this.btnStopDis)
      }
    },
    clear() { // 重置
      // var varArea=document.getElementById('varArea');
      this.inputMessage = "";
      this.rec_text = "";
      this.offline_text = "";
    },
    getJsonMessage(jsonMsg) { // 获取识别结果
      console.log("###################getJsonMessage: " + JSON.parse(jsonMsg.data)['text']);
      var rectxt = "" + JSON.parse(jsonMsg.data)['text'];
      var asrmodel = JSON.parse(jsonMsg.data)['mode'];
      var timestamp = ''; // 返回语音录制的秒数，目前在ws下有值，wss未定义
      if (asrmodel == "2pass-offline" || asrmodel == "offline") {
        this.offline_text = this.offline_text + wss.handleWithTimestamp(rectxt, timestamp); // rectxt; //.replace(/ +/g,"");
        this.rec_text = this.offline_text;
      } else {
        this.rec_text = this.rec_text + rectxt; //.replace(/ +/g,"");
      }
      this.inputMessage = this.rec_text;
      console.log("rec_text: " + this.rec_text);
    },
    recProcess(buffer, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) { // （麦克风）实时处理录音数据                                                                                                                                                                                                                                          
      this.sampleBuf = new Int16Array()
      let sendBuf
      if (this.isRec === true) {
        var data_48k = buffer[buffer.length - 1];
        var array_48k = new Array(data_48k);
        var data_16k = Recorder.SampleData(array_48k, bufferSampleRate, 16000).data;
        this.sampleBuf = Int16Array.from([...this.sampleBuf, ...data_16k]);
        var chunk_size = 960; // for asr chunk_size [5, 10, 5]
        while (this.sampleBuf.length >= chunk_size) {
          sendBuf = this.sampleBuf.slice(0, chunk_size);
          this.sampleBuf = this.sampleBuf.slice(chunk_size, this.sampleBuf.length);
          try {
            this.wsconnecter.wsSend(sendBuf);
          } catch (error) {
            this.stop();
          }
        }
      }
    },
    // 录音结束

    // 新问题
    async createNewSession() {
      // 新问题首先发消息
      this.currentMessages = []
      this.historyList = await langC.threadList({ status: "idle", offset: 0, limit: 1, metadata: { openId: this.CustomerInfo.openId, deptId: this.CustomerInfo.deptId, type: "user", key: this.selVal.value, assistant_id: this.agent.assistant_id } })
    },
    // 填充当前问题的历史内容currentSession
    async getThread(id) {
      this.thread = await langC.threadGet(id)
      this.currentMessages = this.thread.values.messages
      // console.log("getThread:",this.thread)
    },
    async submitQues() {
      if (!this.inputMessage.length) {
        this.$modal.msgWarning("请输入有效的提问信息");
        return false
      }

      // 正在执行？
      if (!this.isEnd) {
        this.$modal.msgWarning("请终止现有提问");
        return false
      }

      let quesStr = this.inputMessage
      // 开始处理提问的内容
      // if (this.quesCarry.format != '') {
      //   if (this.currentMessages.length == 1) { // 第一次提交问题
      //     //  quesCarry.format  '用户提问：{quesStr}. 首次携带：{firstData}. 始终携带：{alwaysData}. 实际问答中根据用户的意向调整推荐产品'
      //     quesStr = this.quesCarry.format.replace('{quesStr}', this.inputMessage).replace('{firstData}', this.quesCarry.first).replace('{alwaysData}', this.quesCarry.always)
      //   } else {
      //     quesStr = this.quesCarry.format.replace('{quesStr}', this.inputMessage).replace('{alwaysData}', this.quesCarry.always)
      //   }
      // }

      let msgArr = [{ type: "human", name: "user", content: quesStr, response_metadata: { ques: this.inputMessage }, assistant_id: this.agent.assistant_id, isShow: true, isSub: true }, { type: "ai", name: "", content: " ", generating: true, isShow: true, isSub: false }]
      // 重置页面的数据和状态
      this.inputMessage = ''
      this.isEnd = false
      this.isUserEnd = false

      if (this.currentMessages.length == 1) { // 第一次提交问题

        // this.thread = await langC.threadCreate({
        //   "thread_id": "",
        //   "metadata": { graph_id: "agent", ques: msgArr[0].response_metadata.content, uuid: this.openId, type: "user", key: this.selVal.value },
        //   "if_exists": "raise",
        // })

        this.currentMessages = msgArr
      } else {
        this.currentMessages.push(...msgArr)
      }
      console.log("currentMessages:", this.currentMessages)
      // 提交问题后，带入线程,无线程时,重新生成
      if (!this.thread) {
        // meirong现有逻辑绝对有线程
        // this.thread = await langC.threadCreate({
        //   "thread_id": "",
        //   "metadata": { graph_id: "agent", ques: msgArr[0].content, uuid: this.openId, type: "user", key: this.selVal.value },
        //   "if_exists": "raise",
        // })
      }
      // 语音预回答
      console.log("语音预回答:", this.audioRoleKey)
      this.fetchToSpeech({ text: "", to_i: -1, msg_i: 0, voice: this.audioRoleKey.voice, prompt: this.audioRoleKey.prompt, msg_content: "好的，我来为您解答疑问。", all: true, toSpeechtest: 0, form: "getLocalData", })
      let localData = null
      // if(this.isLocalData){
      //   localData = await this.getLocalData(msgArr[0]); // 等待 Promise 完成
      //   if(!localData){
      //     this.$modal.msgWarning("本地数据获取失败");
      //   }
      // }
      this.chatUserCLickStream(localData)
    },
    // 请求本地服务中的数据+整理最终请求数据
    async getLocalData(msgData) {

      try {
        const res = await getData(this.selVal.value, msgData.content); // 直接 await
        console.log("localLimit:", this.localLimit);
        let data = [];
        for (let i = 0; i < this.localLimit; i++) {
          data.push(res.documents[i]);
        }
        const localContent = `你是用于回答问题的助手。请使用以下检索到的上下文来回答问题。如果你不知道答案，就直接说你不知道，使用表情增强语气，请使用简体中文回答。问题： {${res.prompt}} 上下文：{${JSON.stringify(data)}} 回答:`;
        return { type: "human", name: "local", content: localContent };
      } catch (error) {
        console.error("获取数据失败:", error);
        return null;
      }
    },
    // stream:true 开始
    // 异步流式--提交
    async chatUserCLickStream(localData) {
      this.isThink = false // 调整传输数据状态

      let messages = []
      // 获取this.currentMessages中的内容，把isSub=true的整理成messages数组
      for (let i = 0; i < this.currentMessages.length; i++) {
        if (this.currentMessages[i].isSub) {
          this.currentMessages[i].isSub = false //修改状态，防止多次提交
          messages.push(this.currentMessages[i])
        }
      }

      // if(localData){
      //   messages.push(localData)
      // }
      // addConfig(JSON.stringify(messages)).then(res => {
      //   console.log("存储json信息",res)
      // })
      // console.log("messages",messages)
      // addConfig(messages).then(res => {
      //   console.log("提交json数据", res)
      // })
      console.log(this.agent)
      console.log("messages", this.thread.thread_id, this.agent.assistant_id,
        { assistant_id: this.agent.assistant_id, input: { messages }, streamMode: ["updates", "messages"] })
      let a = langC.runsStream(
        this.thread.thread_id, this.agent.assistant_id,
        { assistant_id: this.agent.assistant_id, input: { messages }, streamMode: ["updates", "messages"] })

      for await (const chunk of a) {

        if (this.isContent && chunk.event == "messages/partial") {
          if (this.isEnd || this.isUserEnd) {
            return false
          }
          this.getText(chunk.data[0].content)
          this.currentMessages[this.currentMessages.length - 1].generating = false
          if (chunk.data[0].response_metadata.finish_reason == "stop" || chunk.data[0].response_metadata.done_reason == "stop") {
            this.currentMessages[this.currentMessages.length - 1].finish = true
            this.isEnd = true // 重置状态
            this.isUserEnd = false
            this.isThink = false
            this.isContent = false
            console.error("重置状态")
          }

        } else if (chunk.event == "updates") {
          if (chunk.data.retrieve) {
            this.isContent = true
            this.isThink = false
          }
          if (chunk.data.retrieve && chunk.data.retrieve.retrieved_docs.length) {
            const docs = chunk.data.retrieve.retrieved_docs
            for (let d = 0; d < docs.length; d++) {
              const str = docs[d].page_content
              if (str.indexOf('resStart:') >= 0) {
                // const arrayStr = resDocs
                const arrayStr = str.split('resStart:')[1]; // 分割字符串，取数组部分
                // 2. 替换单引号为双引号（JSON 要求双引号）
                const jsonStr = arrayStr.split("'").join('"');
                // 3. 解析为 JSON 对象
                console.log("resDocs", jsonStr)
                this.currentMessages[this.currentMessages.length - 1].resourceList = JSON.parse(jsonStr);
                break
              }
            }
          }
        } else if (chunk.event == "error") {
          this.isEnd = true // 重置状态
          this.isUserEnd = true
          this.isThink = false
          this.isContent = false
          this.$message.warning('网络异常，请重新提问！')
        }

        // console.log(chunk);
      }
    },
    // 异步提交后，处理返回数据
    async getText(content) {
      // 用户可能会中断问答
      if (this.isEnd || this.isUserEnd) {
        console.error("getText-回答中断[进程，用户]", this.isEnd, this.isUserEnd)
        return false
      }
      const msgI = this.currentMessages.length - 1 // 当前数据下标
      // let msg = "关于[" + this.currentMessages[msgI - 1].content.replace(/\n/g, '') + "]的问题，您需要了解以下信息。\n " + content

      // 返回回答的部分内容中,每次返回几个字符码，可能会包含推荐产品的信息标记<#$&{"CID":"1","PID":"1"}#$&>
      // 将<#$&  #$&>之间的json数据单独放到一个变量中this.productInfo

      this.currentMessages[msgI].content = content

      let msg = this.currentMessages[msgI].content

      // 查找并处理产品推荐信息
      const productInfoRegex = /<#\$&(\{[^}]*"CID"[^}]*"PID"[^}]*\})#\$&>/g;

      let match;
      let hasProductInfo = false;

      while ((match = productInfoRegex.exec(msg)) !== null) {
        // 提取JSON数据
        try {
          const productInfo = JSON.parse(match[1]);
          this.productInfo = productInfo;
          hasProductInfo = true;
          // console.log("产品信息:", this.productInfo);

          // 从原内容中移除产品信息标记
          msg = msg.replace(match[0], '');
        } catch (e) {
          console.error("解析产品信息失败:", e);
        }
      }

      if (hasProductInfo) {
        // 更新消息内容
        this.currentMessages[msgI].content = msg;
        // console.log("剩余信息:", this.currentMessages[msgI].content);
        // 使用emit通知父组件
        this.$emit('product-info-updated', this.productInfo);
      }


      // 主逻辑修改
      let msgArr = msg.split(this.toSpeechtest);
      // 处理超长子串，生成新的数组（保证所有子串≤140长度）
      const processedArr = [];
      msgArr.forEach(part => {
        // 对每个子串检查长度，超长则拆分
        if (part.length > 140) {
          processedArr.push(...this.processLongText(part));
        } else {
          processedArr.push(part);
        }
      });
      // 替换为处理后的数组
      msgArr = processedArr;
      // 需要验证每个下标的字符长度，如果大于140则将文本中140字符前的最后一个逗号改为句号，并重新split，
      // // 携带截取后的数据下标，保证异步写入顺序正常
      const key = this.toSpeechKey
      // console.log(msgI + "==msgI携带截取后的数据下标，保证异步写入顺序正常:", key + "<" + parseInt(msgArr.length - 1)
      // , msgArr.length, msgArr)
      // console.warn("之前", this.toSpeechKey, parseInt(msgArr.length - 1), msgArr)
      if (key < parseInt(msgArr.length - 1)) {
        this.fetchToSpeech({ text: msgArr[key], to_i: key, msg_i: msgI, msg_content: "", all: false, voice: this.audioRoleKey.voice, prompt: this.audioRoleKey.prompt, form: "生成", })
        this.toSpeechKey++
        // console.warn("之后", this.toSpeechKey)
      }
    },
    // stream:true 结束
    // 处理超长子串的函数：将超过140长度的文本按逗号拆分（改逗号为句号）
    processLongText(text, maxLen = 140) {
      const result = [];
      let current = text;

      while (current.length > maxLen) {
        // 查找140字符内最后一个逗号的位置
        let commaPos = -1;
        // 从maxLen往前找，最多查20个字符（避免过度回溯）
        const searchEnd = Math.min(maxLen, current.length);
        const searchStart = Math.max(0, searchEnd - 20);

        for (let i = searchEnd; i >= searchStart; i--) {
          if (current[i] === '，') {
            commaPos = i;
            break;
          }
        }

        if (commaPos !== -1) {
          // 找到逗号：将逗号改为句号，截取前半部分
          const part = current.substring(0, commaPos) + '。';
          result.push(part);
          // 剩余部分继续处理
          current = current.substring(commaPos + 1);
        } else {
          // 没找到逗号：直接按maxLen截断（避免无限循环）
          result.push(current.substring(0, maxLen));
          current = current.substring(maxLen);
        }
      }

      // 添加剩余部分（≤maxLen）
      if (current) {
        result.push(current);
      }

      return result;
    },
    // 中断回答操作，重置数据
    cancelRequest() {
      this.isEnd = true;
      this.isUserEnd = true;
      // 重置语音状态
      this.audioStop(0)
      this.audioInit(0)
      // 消息列表
      this.currentMessages[this.currentMessages.length - 1].content = "您中断了回答，可以继续提问...."
      this.currentMessages[this.currentMessages.length - 1].generating = false
    },

    // 格式化返回内容
    upContent(text) {
      if (text) {
        return markdown.returnContent(text.replace('并且以中文回复，回复内容中不包含附件参考信息', ''))
      }
    },
    // 选择问答模块切换
    upSelVal(i) {
      this.selVal = this.selOp[i]
    },
    // 动态对话框
    scrollToBottom() {
      this.$nextTick(() => {
        const container = this.$refs.messageContainer;
        if (container) {
          container.scrollTop = container.scrollHeight;
        }
      });
    },
    // 启动重命名
    startRename(item, isRightSide = false) {
      this.renamingId = item.thread_id
      this.editText = item.metadata.ques
      this.originalText = item.metadata.ques

      this.$nextTick(() => {
        const refName = isRightSide ? 'renameInputRight' : 'renameInputLeft'
        if (this.$refs[refName] && this.$refs[refName][0]) {
          this.$refs[refName][0].focus()
        }
      })
    },

    // 提交重命名
    async submitRename(item) {
      if (this.renamingId && this.editText.trim() && this.editText !== this.originalText) {
        try {
          item.metadata.ques = this.editText
          await langC.threadUpdate(item.thread_id, { metadata: item.metadata })
          this.$message.success('重命名成功')
        } catch (error) {
          this.$message.error('重命名失败')
          item.metadata.ques = this.originalText
        }
      }
      this.cancelRename()
    },

    // 取消重命名
    cancelRename() {
      this.renamingId = null
      this.editText = ''
      this.originalText = ''
    },
    deleteHistory(thread_id) {
      this.$confirm('确定要删除该历史记录吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        try {
          await langC.threadDelete(thread_id)
          this.$message.success('删除成功');
          this.initLang()
        } catch (error) {
          console.log(error)
        }
      })
    },
  }
}
</script>

<style scoped>
.main-container1 {
  display: flex;
}

.main-card {
  width: 100%;
  position: relative;
}

.card-header {
  border-radius: 15px 15px 0 0;
  height: 100px;
  background: -webkit-linear-gradient(to right, #409eff, #B271B7, #409eff, #2E82FC, #1890FF, #409eff, #1890FF, #409eff);
  background: linear-gradient(to right, #409eff, #B271B7, #409eff, #2E82FC, #1890FF, #409eff, #1890FF, #409eff);
}

.header-img {
  height: 100%;
}

.header-img img {
  height: 120%;
}

.header-title {
  height: 100%;
  line-height: 80px;
  padding-left: 30px;
  color: #fff;
  font-size: 23px;
  letter-spacing: 8px;
}

.header-title>div {
  height: 100%;
  overflow: hidden;
}

.card-content {
  margin-top: -20px;
  height: calc(100% - 120px);
  display: flex;
  overflow: hidden;
}



.rainbow-text {
  color: #000;
  /* 设置基础文字颜色 */
  background: -webkit-linear-gradient(to right, #1890FF, #181cff, #B271B7);
  background: linear-gradient(to right, #1890FF, #181cff, #B271B7);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.p-div {
  line-height: 35px;
  font-size: 15px;
  color: rgba(0, 0, 0, 0.8);
}

.p-dian {
  position: relative;
  padding-left: 20px;
}

.p-dian::before {
  content: " ";
  position: absolute;
  top: 15px;
  left: 10px;
  height: 3px;
  width: 3px;
  border-radius: 3px;
  background: #246BD4;
}




.history-sidebar {
  width: 15%;
  background: #ffffff;
  box-shadow: 2px 0 6px rgba(0, 0, 0, 0.1);
  transition: width 0.3s ease;
  overflow: hidden;
}

.sidebar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px;
  border-bottom: 1px solid #ebeef5;
}

.history-list {
  height: calc(100vh - 75px);
  overflow-y: auto;
}

.history-item {
  padding: 12px;
  border-bottom: 1px solid #f0f2f5;
  cursor: pointer;
}

.history-item:hover {
  background: #f5f7fa;
}

.history-item.active {
  background: #e8f4ff;
}

.history-title {
  font-weight: 500;
  color: #303133;
  margin-bottom: 4px;
}

.history-preview {
  font-size: 12px;
  color: #909399;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.main-content {
  flex: 1;
  display: flex;
  height: 100%;
  flex-direction: column;
}

.message-container {
  flex: 1;
  overflow-y: auto;
  padding: 20px 0 180px;
}

.message-item {
  display: flex;
  margin-bottom: 20px;
}

.message-item.human {
  flex-direction: row-reverse;
}


.message-avatar {
  width: 40px;
  height: 40px;
  margin: 0 12px;
}

.message-avatar img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
}

.message-bubble {

  max-width: 80%;
  overflow: hidden;
  padding: 10px 16px;
  border-radius: 20px;
  position: relative;
  box-shadow: rgba(100, 100, 100, 0.1) 2px 3px 5px;
}

.message-item.human .message-bubble {
  background: rgb(110, 177, 248);
  color: white;
  padding: 3px 16px 2px;
  margin-right: 10px;
}

.message-item.ai .message-bubble,
.message-item.system .message-bubble {
  background: white;
  border: 1px solid #ebeef5;
}

.message-text {
  line-height: 1.5;
  font-size: inherit;
}

.content-container * {
  /* 作用于所有子元素，优先级高于继承 */
  font-size: inherit !important;
}

.message-time {
  font-size: 12px;
  color: #909399;
  margin-top: 6px;
}

.input-div {
  background: #ffffff;
  padding: 10px 15px;
  box-shadow: 0 0 5px 5px #ffffff;
  position: absolute;
  bottom: 0;
  right: 0;
  left: 0;
}

.input-bg {
  background: #fff;
  border-radius: 20px;
  box-shadow: rgba(100, 100, 100, 0.1) 1px 2px 3px;
  position: relative;
  border: 1px solid #ebeef5;

}

.input-div {
  /* margin:0 150px 15px;
  display: flex; */
}

.input-area {
  position: relative;
  left: 25px;
  bottom: 5px;
  width: calc(100% - 110px);
}

.action-bar {
  margin-top: 12px;
  text-align: right;
}

pre {
  background: #f6f8fa;
  padding: 12px;
  border-radius: 4px;
  overflow-x: auto;
}

code {
  font-family: monospace;
}

/* 等待效果 */
/* 加载动画样式 */
.loading-spinner {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 30px;
}

.spinner {
  width: 18px;
  height: 18px;
  border: 2px solid #f3f3f3;
  border-top: 2px solid #409eff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

/* 调整消息气泡样式 */
.message-bubble.generating {
  min-height: 80px;
  padding: 20px;
}


.spinner {
  width: 28px;
  /* 调整大小 */
  height: 28px;
  border-width: 4px;
  /* 调整边框粗细 */
  border-top-color: #67C23A;
  /* 修改颜色 */
  animation-duration: 0.6s;
  /* 调整旋转速度 */
}


/* 样式优化 */
.leftDiv {
  width: 150px;
  margin: 3px 15px 0 0;
}

.iconBtn {
  color: #727272;
}

.message-text .think {
  background: #f5f5f5;
  color: #727272;
  font-size: 14px;
  padding: 20px;
  border-radius: 10px;
}

.stAudio {
  position: fixed;
  top: -20%;
  right: -300px;
  opacity: 0;
  z-index: -5;
}

.auto-audio-role img {
  width: 50px;
  display: block;
  margin-bottom: 5px;
}

.auto-audio-role button {
  padding: 0 0 5px;
  overflow: hidden;
}


.auto-audio-role.rensheng button {
  width: 46%;
  margin: 2%;
}

.auto-audio-role.rensheng button img {
  width: 100%;
}


.icon-shouye {
  position: absolute;
  top: 25px;
  right: 25px;
  font-size: 25px;
  color: #fff;
  z-index: 5;
}

.hotQuesDiv {
  display: flex;
  flex-wrap: nowrap;
  overflow: auto;
  margin-bottom: 10px;
}

.hotQuesDiv>div {
  padding: 5px 10px;
  margin-right: 10px;
  font-size: 14px;
  text-wrap: nowrap;
  white-space: nowrap;
  color: #777;
  text-align: center;
}

/* 右侧消失，左侧全屏 */
@media(max-width:1400px) {
  .card-content-right {
    display: none;
  }

  .card-content-left {
    width: 100%;
  }
}


@media(min-width:993px) {
  .input-bg {
    padding: 25px 15px;
  }
}

@media(max-width:992px) {

  /* 政府页面效果去除 */
  .card-header {
    display: none;
  }

  .card-content {
    margin-top: 0;
  }

  .main-container1 {
    padding: 0;
  }

  .card-content {
    border-radius: 0;
  }


  /* .input-div{margin:0;} */
  .message-container {
    padding: 10px 0 10px;
  }

  /* .Btn{height:30px;width:30px;display: block;} */
  .input-div {
    padding: 10px 15px 5px
  }

  /* 对话框 */
  .message-item {
    margin-bottom: 5px;
  }

  .message-bubble {
    max-width: calc(100vw - 45px);
  }

  .message-item.human .message-bubble {
    margin: 5px 5px 5px 0;
  }

  .message-item.system .message-bubble,
  .message-item.ai .message-bubble {
    margin: 5px 0 5px 5px;
  }

  .message-avatar {
    width: 30px;
    height: 30px;
    margin: 0 0 0 5px;
  }

  .icon-shouye {
    top: 15px;
    right: 10px;
    color: rgba(0, 0, 0, 0.7);
  }
}

@media (max-width: 600px) {
  .input-bg {
    padding: 0 10px !important;
  }
}
</style>